lib/repo: Add min-free-space-size option
authorUmang Jain <umang@endlessm.com>
Mon, 11 Jun 2018 21:06:31 +0000 (02:36 +0530)
committerAtomic Bot <atomic-devel@projectatomic.io>
Wed, 13 Jun 2018 18:57:37 +0000 (18:57 +0000)
Similar to min-free-space-percent but it supports specific sizes
(in MB, GB or TB). Also, making min-free-space-percent and -size
mutually exclusive.

min-free-space-percent does not give a fine tuning of the free disk
space that a user might decide to keep. It can translate to very large
size (e.g. 1% = ~10GB on 1TB HDD) or very small (e.g. 1% = ~330MB on 32GB
system like Endless devices). Hence, it makes sense to introduce a config
option to honor specific size as per the user.

Closes: #1616
Approved by: jlebon

man/ostree.repo-config.xml
src/libostree/ostree-repo-commit.c
src/libostree/ostree-repo-private.h
src/libostree/ostree-repo.c
tests/installed/nondestructive/itest-pull-space.sh

index cbc605f7758fa9d4a5c60de52c3d14502e32a025..5a95cafaba89bf58d02c098dfdd03b655d0e1964 100644 (file)
@@ -124,6 +124,15 @@ Boston, MA 02111-1307, USA.
         keep free. The default value is 3.</para></listitem>
       </varlistentry>
 
+     <varlistentry>
+        <term><varname>min-free-space-size</varname></term>
+        <listitem><para>Value (in MB, GB or TB) that specifies a minimum space (in blocks)
+        in the underlying filesystem to keep free. Also, note that min-free-space-percent
+        and min-free-space-size are mutually exclusive. Examples of acceptable values:
+        500MB, 1GB etc.
+        </para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>add-remotes-config-dir</varname></term>
         <listitem>
index 8285a1a0c15b74ca13ce24e18b8a34defbba69bb..06ade885a88a940b38b6c71b109d48e122c4e5cb 100644 (file)
@@ -889,7 +889,7 @@ write_content_object (OstreeRepo         *self,
     size = 0;
 
   /* Free space check; only applies during transactions */
-  if (self->min_free_space_percent > 0 && self->in_transaction)
+  if ((self->min_free_space_percent > 0 || self->min_free_space_size > 0) && self->in_transaction)
     {
       g_mutex_lock (&self->txn_lock);
       g_assert_cmpint (self->txn.blocksize, >, 0);
@@ -898,8 +898,12 @@ write_content_object (OstreeRepo         *self,
         {
           g_mutex_unlock (&self->txn_lock);
           g_autofree char *formatted_required = g_format_size ((guint64)object_blocks * self->txn.blocksize);
-          return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s more required",
-                             self->min_free_space_percent, formatted_required);
+          if (self->min_free_space_percent > 0)
+            return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s more required",
+                               self->min_free_space_percent, formatted_required);
+          else
+            return glnx_throw (error, "min-free-space-size %luMB would be exceeded, %s more required",
+                               self->min_free_space_size, formatted_required);
         }
       /* This is the main bit that needs mutex protection */
       self->txn.max_blocks -= object_blocks;
@@ -1491,6 +1495,25 @@ devino_cache_lookup (OstreeRepo           *self,
   return dev_ino_val->checksum;
 }
 
+static guint64
+min_free_space_calculate_reserved_blocks (OstreeRepo *self, struct statvfs *stvfsbuf)
+{
+  guint64 reserved_blocks = 0;
+
+  if (self->min_free_space_size > 0)
+    {
+      reserved_blocks = (self->min_free_space_size << 20) / stvfsbuf->f_bsize;
+    }
+  else if (self->min_free_space_percent > 0)
+    {
+      /* Convert fragment to blocks to compute the total */
+      guint64 total_blocks = (stvfsbuf->f_frsize * stvfsbuf->f_blocks) / stvfsbuf->f_bsize;
+      reserved_blocks = ((double)total_blocks) * (self->min_free_space_percent/100.0);
+    }
+
+  return reserved_blocks;
+}
+
 /**
  * ostree_repo_scan_hardlinks:
  * @self: An #OstreeRepo
@@ -1572,26 +1595,28 @@ ostree_repo_prepare_transaction (OstreeRepo     *self,
     return FALSE;
 
   self->in_transaction = TRUE;
-  if (self->min_free_space_percent > 0)
+  if (self->min_free_space_percent >= 0 || self->min_free_space_size >= 0)
     {
       struct statvfs stvfsbuf;
       if (TEMP_FAILURE_RETRY (fstatvfs (self->repo_dir_fd, &stvfsbuf)) < 0)
         return glnx_throw_errno_prefix (error, "fstatvfs");
       g_mutex_lock (&self->txn_lock);
       self->txn.blocksize = stvfsbuf.f_bsize;
-      /* Convert fragment to blocks to compute the total */
-      guint64 total_blocks = (stvfsbuf.f_frsize * stvfsbuf.f_blocks) / stvfsbuf.f_bsize;
+      guint64 reserved_blocks = min_free_space_calculate_reserved_blocks (self, &stvfsbuf);
       /* Use the appropriate free block count if we're unprivileged */
       guint64 bfree = (getuid () != 0 ? stvfsbuf.f_bavail : stvfsbuf.f_bfree);
-      guint64 reserved_blocks = ((double)total_blocks) * (self->min_free_space_percent/100.0);
       if (bfree > reserved_blocks)
         self->txn.max_blocks = bfree - reserved_blocks;
       else
         {
           g_mutex_unlock (&self->txn_lock);
           g_autofree char *formatted_free = g_format_size (bfree * self->txn.blocksize);
-          return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s available",
-                             self->min_free_space_percent, formatted_free);
+          if (self->min_free_space_percent > 0)
+            return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s available",
+                               self->min_free_space_percent, formatted_free);
+          else
+            return glnx_throw (error, "min-free-space-size %luMB would be exceeded, %s available",
+                               self->min_free_space_size, formatted_free);
         }
       g_mutex_unlock (&self->txn_lock);
     }
index 63aa451ab4ab7e99aaea9f07184e54f65dcb1154..eefb80e3caefc05ee34b67136602443f55234799 100644 (file)
@@ -151,6 +151,7 @@ struct OstreeRepo {
   uid_t target_owner_uid; /* Ensure files are chowned to this uid/gid */
   gid_t target_owner_gid;
   guint min_free_space_percent; /* See the min-free-space-percent config option */
+  guint64 min_free_space_size; /* See the min-free-space-size config option */
 
   guint test_error_flags; /* OstreeRepoTestErrorFlags */
 
index f3da1cae5111940daf210776c863ead5812fa2db..fa5a9bf87cdc44ee8a4f087faf39ebfcdb959a27 100644 (file)
@@ -2654,6 +2654,48 @@ get_remotes_d_dir (OstreeRepo          *self,
     return g_file_resolve_relative_path (sysroot, SYSCONF_REMOTES);
 }
 
+static gboolean
+min_free_space_size_validate_and_convert (OstreeRepo    *self,
+                                          const char    *min_free_space_size_str,
+                                          GError       **error)
+{
+  static GRegex *regex;
+  static gsize regex_initialized;
+  if (g_once_init_enter (&regex_initialized))
+    {
+      regex = g_regex_new ("^([0-9]+)(G|M|T)B$", 0, 0, NULL);
+      g_assert (regex);
+      g_once_init_leave (&regex_initialized, 1);
+    }
+
+  g_autoptr(GMatchInfo) match = NULL;
+  if (!g_regex_match (regex, min_free_space_size_str, 0, &match))
+    return glnx_prefix_error (error, "Error parsing min-free-space-size parameter: '%s'", min_free_space_size_str);
+
+  g_autofree char *size_str = g_match_info_fetch (match, 1);
+  g_autofree char *unit = g_match_info_fetch (match, 2);
+  guint shifts;
+
+  switch (*unit)
+    {
+      case 'M':
+        shifts = 0;
+        break;
+      case 'G':
+        shifts = 10;
+        break;
+      case 'T':
+        shifts = 20;
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  self->min_free_space_size = g_ascii_strtoull (size_str, NULL, 10) << shifts;
+
+  return TRUE;
+}
+
 static gboolean
 reload_core_config (OstreeRepo          *self,
                     GCancellable        *cancellable,
@@ -2771,18 +2813,39 @@ reload_core_config (OstreeRepo          *self,
       self->zlib_compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL;
   }
 
-  { g_autofree char *min_free_space_percent_str = NULL;
-    /* If changing this, be sure to change the man page too */
-    const char *default_min_free_space = "3";
+  {
+    if (g_key_file_has_key (self->config, "core", "min-free-space-size", error) &&
+        g_key_file_has_key (self->config, "core", "min-free-space-percent", error))
+      {
+        return glnx_throw (error, "min-free-space-percent and min-free-space-size are mutually exclusive.");
+      }
+    else if (g_key_file_has_key (self->config, "core", "min-free-space-size", error))
+      {
+        g_autofree char *min_free_space_size_str = NULL;
 
-    if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent",
-                                            default_min_free_space,
-                                            &min_free_space_percent_str, error))
-      return FALSE;
+        if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-size",
+                                                NULL, &min_free_space_size_str, error))
+          return FALSE;
 
-    self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10);
-    if (self->min_free_space_percent > 99)
-      return glnx_throw (error, "Invalid min-free-space-percent '%s'", min_free_space_percent_str);
+        /* Validate the string and convert the size to MBs */
+        if (!min_free_space_size_validate_and_convert (self, min_free_space_size_str, error))
+          return glnx_throw (error, "Invalid min-free-space-size '%s'", min_free_space_size_str);
+      }
+    else
+      {
+        g_autofree char *min_free_space_percent_str = NULL;
+        /* If changing this, be sure to change the man page too */
+        const char *default_min_free_space = "3";
+
+        if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent",
+                                                default_min_free_space,
+                                                &min_free_space_percent_str, error))
+          return FALSE;
+
+        self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10);
+        if (self->min_free_space_percent > 99)
+          return glnx_throw (error, "Invalid min-free-space-percent '%s'", min_free_space_percent_str);
+      }
   }
 
   {
index 925629b26ce0592044f7019ceacf96fbe4aca94f..bb3f1546cdecb27b1dbfe91f526cb64c28cde4e8 100755 (executable)
@@ -16,12 +16,27 @@ blkdev=$(losetup --find --show $(pwd)/testblk.img)
 mkfs.xfs ${blkdev}
 mkdir mnt
 mount ${blkdev} mnt
+
+# first test min-free-space-percent
 ostree --repo=mnt/repo init --mode=bare-user
 echo 'fsync=false' >> mnt/repo/config
 if ostree --repo=mnt/repo pull-local /ostree/repo ${host_commit} 2>err.txt; then
     fatal "succeeded in doing a pull with no free space"
 fi
 assert_file_has_content err.txt "min-free-space-percent"
+echo "ok min-free-space-percent"
+
+# now test min-free-space-size
+rm -rf mnt/repo
+ostree --repo=mnt/repo init --mode=bare-user
+echo 'fsync=false' >> mnt/repo/config
+echo 'min-free-space-size=10MB' >> mnt/repo/config
+if ostree --repo=mnt/repo pull-local /ostree/repo ${host_commit} 2>err.txt; then
+    fatal "succeeded in doing a pull with no free space"
+fi
+assert_file_has_content err.txt "min-free-space-size"
+echo "ok min-free-space-size"
+
 umount mnt
 losetup -d ${blkdev}
 date